import { ts,  NodePredicate } from 'qvog-dsl';
import { native, HomeCheckHint } from 'qvog-dsl';

const SEVERITY = 2;
const DOC_PATH = '';
const DESCRIPTION = 'Enforce consistent spacing before function parenthesis';

type SpaceOption = 'always' | 'never' | 'ignore';
type Options = [
    {
        anonymous: SpaceOption;
        named: SpaceOption;
        asyncArrow: SpaceOption;
    }
];

export const Target: NodePredicate<ts.Node> = {
    is(node: ts.Node): node is ts.Node {
        return ts.isFunctionDeclaration(node) || 
        ts.isFunctionExpression(node) || 
        ts.isArrowFunction(node) || 
        ts.isMethodDeclaration(node) || 
        ts.isConstructorDeclaration(node) || 
        ts.isGetAccessor(node) || 
        ts.isSetAccessor(node);
    },
};

export default native<HomeCheckHint>()()
    .match(Target)
    .when((node: ts.Node): boolean => {
        let options = getDefaultOption()[0];
        let code = node.getSourceFile().getFullText();
        if (ts.isFunctionDeclaration(node)) {
            return checkNamedFunction(node, code, options);
        } else if (ts.isFunctionExpression(node)) {
            return checkFunctionExpression(node, !!node.name, code, options);
        } else if (ts.isArrowFunction(node)) {
            return checkAsyncArrowFunction(node, code, options);
        } else if (ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node)) {
            return checkMethod(node, code, options);
        } else if (ts.isConstructorDeclaration(node)) {
            return checkConstructor(node, code, options);
        }
        return false;
    })
    .report({
        severity: SEVERITY,
        description: DESCRIPTION,
        docPath: DOC_PATH,
    });

function getParenPosition(node: ts.FunctionLikeDeclaration): number | null {
    const openParen = node.getChildren().find(c => c.kind === ts.SyntaxKind.OpenParenToken);
    return openParen?.getStart() || null;
}

function checkNamedFunction(node: ts.FunctionDeclaration, code: string, options: Options[0]): boolean {
    if (!node.name?.getText() && isGeneratorFunction(node)) {
        return false;
    }
    if (options.named === 'ignore') {
        return false;
    }

    if (!node.name) {
        const isExportDefault = node.modifiers?.some(m =>
            m.kind === ts.SyntaxKind.ExportKeyword &&
            node.modifiers?.some(m2 => m2.kind === ts.SyntaxKind.DefaultKeyword)
        );
        if (!isExportDefault) {
            return false;
        }
        const functionKeyword = node.getChildren().find(c => c.kind === ts.SyntaxKind.FunctionKeyword);
        if (!functionKeyword) {
            return false;
        }
        const checkStart = functionKeyword.end;
        const parenPos = getParenPosition(node);
        if (!parenPos) {
            return false;
        }
        const errorMessage = options.named === 'always'
            ? 'Missing space before function parentheses.'
            : 'Unexpected space before function parentheses.';
        return checkSpace(node, code, checkStart, parenPos, options.named, errorMessage);
    }
    let checkStart;
    if (node.typeParameters && node.typeParameters.length > 0) {
        const lastTypeParam = node.typeParameters[node.typeParameters.length - 1];
        checkStart = lastTypeParam.getEnd();
        const closingAngle = findClosingAngleBracket(code, checkStart);
        if (closingAngle !== -1) {
            checkStart = closingAngle + 1;
        }
    } else {
        checkStart = node.name.end;
    }
    const parenPos = getParenPosition(node);
    if (!parenPos) {
        return false;
    }
    const errorMessage = options.named === 'always'
        ? 'Missing space before function parentheses.'
        : 'Unexpected space before function parentheses.';

    return checkSpace(node, code, checkStart, parenPos, options.named, errorMessage);
}

// 查找泛型参数后的右尖括号
function findClosingAngleBracket(code: string, startPos: number): number {
    let pos = startPos;
    while (pos < code.length) {
        if (code[pos] === '>') {
            return pos;
        }
        pos++;
    }
    return -1;
}

function checkAsyncArrowFunction(node: ts.ArrowFunction, code: string, options: Options[0]): boolean {
    if (options.asyncArrow === 'ignore') {
        return false;
    }
    const asyncKeyword = node.modifiers?.find(m => m.kind === ts.SyntaxKind.AsyncKeyword);
    if (!asyncKeyword) {
        return false;
    }
    const parenPos = getParenPosition(node);
    if (!parenPos) {
        return false;
    }
    const errorMessage = options.asyncArrow === 'always'
        ? 'Missing space before function parentheses.'
        : 'Unexpected space before function parentheses.';

    return checkSpace(node, code, asyncKeyword.end, parenPos, options.asyncArrow, errorMessage);
}

function checkMethod(node: ts.MethodDeclaration | ts.GetAccessorDeclaration | ts.SetAccessorDeclaration,
    code: string, options: Options[0]): boolean {
    if (options.named === 'ignore') {
        return false;
    }

    let checkStart = node.name.end;

    // 新增：处理泛型参数
    if (node.typeParameters && node.typeParameters.length > 0) {
        const lastTypeParam = node.typeParameters[node.typeParameters.length - 1];
        checkStart = lastTypeParam.getEnd();
        const closingAngle = findClosingAngleBracketForMethod(code, checkStart);
        if (closingAngle !== -1) {
            checkStart = closingAngle + 1; // 调整检查起点到泛型闭合后
        }
    }

    const parenPos = getParenPosition(node);
    if (!parenPos) {
        return false;
    }

    const errorMessage = options.named === 'always'
        ? 'Missing space before function parentheses.'
        : 'Unexpected space before function parentheses.';

    return checkSpace(node, code, checkStart, parenPos, options.named, errorMessage);
}

function findClosingAngleBracketForMethod(code: string, startPos: number): number {
    let pos = startPos;
    let stack = 1; // 初始栈为1，匹配外层泛型
    while (pos < code.length) {
        const char = code[pos];
        if (char === '<') {
            stack++;
        } else if (char === '>') {
            stack--;
            if (stack === 0) {
                return pos; // 返回最外层闭合的 '>'
            }
        }
        pos++;
    }
    return -1;
}

function checkConstructor(node: ts.ConstructorDeclaration, code: string, options: Options[0]): boolean {
    if (options.named === 'ignore') {
        return false;
    }

    const constructorKeyword = node.getChildren().find(c => c.kind === ts.SyntaxKind.ConstructorKeyword);
    if (!constructorKeyword) {
        return false;
    }

    const checkStart = node.typeParameters?.end || constructorKeyword.end;
    const parenPos = getParenPosition(node);
    if (!parenPos) {
        return false;
    }

    const errorMessage = options.named === 'always'
        ? 'Missing space before function parentheses.'
        : 'Unexpected space before function parentheses.';

    return checkSpace(node, code, checkStart, parenPos, options.named, errorMessage);
}

function isGeneratorFunction(node: ts.FunctionLikeDeclaration): boolean {
    return node.asteriskToken !== undefined;
}

function checkFunctionExpression(node: ts.FunctionExpression, isNamed: boolean,
    code: string, options: Options[0]): boolean {
    if (!isNamed && isGeneratorFunction(node)) {
        return false;
    }

    const option = isNamed ? options.named : options.anonymous;
    if (option === 'ignore') {
        return false;
    }

    const checkStart = node.typeParameters?.end || (isNamed && node.name?.end) ||
        node.getChildren().find(c => c.kind === ts.SyntaxKind.FunctionKeyword)?.end;

    if (!checkStart) {
        return false;
    }

    const parenPos = getParenPosition(node);
    if (!parenPos) {
        return false;
    }

    const errorMessage = option === 'always'
        ? 'Missing space before function parentheses.'
        : 'Unexpected space before function parentheses.';

    return checkSpace(node, code, checkStart, parenPos, option, errorMessage);
}

function checkSpace(
    node: ts.Node,
    code: string,
    checkStart: number,
    parenPos: number,
    optionType: SpaceOption,
    errorMessage: string
): boolean {
    if (optionType === 'ignore' || parenPos === -1) {
        return false;
    }

    const spaceCheckStart = Math.max(checkStart, 0);
    const spaceCheckEnd = parenPos - 1;

    if (optionType === 'never') {
        return checkNeverSpace(node, code, spaceCheckStart, spaceCheckEnd, errorMessage, optionType);
    } else if (optionType === 'always') {
        return checkAlwaysSpace(node, code, spaceCheckStart, spaceCheckEnd, parenPos, errorMessage, optionType);
    }
    return false;
}

function checkNeverSpace(
    node: ts.Node,
    code: string,
    spaceCheckStart: number,
    spaceCheckEnd: number,
    errorMessage: string,
    optionType: SpaceOption
): boolean {
    let hasInvalidSpace = false;
    let pos = spaceCheckStart;
    let firstSpacePos = -1;

    while (pos <= spaceCheckEnd) {
        const char = code[pos];

        // Check for whitespace
        if (/\s/.test(char)) {
            if (firstSpacePos === -1) {
                firstSpacePos = pos;
            }
            hasInvalidSpace = true;
        }

        if (char === '/' && code[pos + 1] === '*') {
            if (firstSpacePos === -1) {
                firstSpacePos = pos;
            }
            hasInvalidSpace = true;
            const commentEnd = code.indexOf('*/', pos + 2);
            if (commentEnd === -1) {
                break;
            }
            pos = commentEnd + 2;
            continue;
        }

        if (char === '/' && code[pos + 1] === '/') {
            if (firstSpacePos === -1) {
                firstSpacePos = pos;
            }
            hasInvalidSpace = true;
            const lineEnd = code.indexOf('\n', pos);
            pos = lineEnd === -1 ? spaceCheckEnd + 1 : lineEnd;
            continue;
        }

        pos++;
    }
    if (hasInvalidSpace && firstSpacePos !== -1) {
        return true;
    }
    return false;
}

function checkAlwaysSpace(
    node: ts.Node,
    code: string,
    spaceCheckStart: number,
    spaceCheckEnd: number,
    parenPos: number,
    errorMessage: string,
    optionType: SpaceOption
): boolean {
    let hasWhitespace = false;
    let pos = spaceCheckStart;
    while (pos <= spaceCheckEnd) {
        const char = code[pos];
        if (/\s/.test(char)) {
            hasWhitespace = true;
            pos++;
            continue;
        }
        if (char === '/' && code[pos + 1] === '*') {
            const commentEnd = code.indexOf('*/', pos + 2);
            if (commentEnd === -1) {
                break;
            }
            pos = commentEnd + 2;
            continue;
        }
        if (char === '/' && code[pos + 1] === '/') {
            const lineEnd = code.indexOf('\n', pos);
            pos = lineEnd === -1 ? spaceCheckEnd + 1 : lineEnd;
            continue;
        }
        break;
    }

    if (!hasWhitespace) {
        return true;
    }
    return false;
}

function getDefaultOption(): Options {
    return [{ anonymous: 'never', named: 'never', asyncArrow: 'never' }];
}
